#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Toolkit.Services;
#endregion

namespace Toolkit
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class Camera : GameComponent
    {
        private GraphicsDeviceManager graphics;
        protected IInputHandler input;

        private Matrix projection;
        private Matrix view;
        private Vector3 startingPosition;
        private Vector3 cameraPosition;
        private Vector3 cameraTarget;
        private Vector3 cameraUpVector;
        private Vector3 cameraReference = new Vector3(0, 0, -1);
        protected Vector3 movement;

        protected float spinRate = 50.0f;
        protected float moveRate = 1000.0f;
        protected bool inputEnabled = true;

        private float cameraYaw = 0.0f;
        private float cameraPitch = 0.0f;

        public Matrix View
        {
            get { return view; }
        }

        public Matrix Projection
        {
            get { return projection; }
        }

        public float SpinRate
        {
            get { return spinRate; }
            set { spinRate = value; }
        }

        public float MoveRate
        {
            get { return moveRate; }
            set { moveRate = value; }
        }

        public bool InputEnabled
        {
            get { return inputEnabled; }
            set { inputEnabled = value; }
        }

        public Camera(Game game, Vector3 startingPosition, float spinSpeed, float moveSpeed)
            : base(game)
        {
            graphics = (GraphicsDeviceManager)Game.Services.GetService(typeof(IGraphicsDeviceManager));
            input = (IInputHandler)Game.Services.GetService(typeof(IInputHandler));

            cameraPosition = startingPosition;
            this.startingPosition = startingPosition;
            spinRate = spinSpeed;
            moveRate = moveSpeed;
        }

        public Camera(Game game)
            : this(game, new Vector3(0, 0, 5000), 2.0f, 15.0f)
        {
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            // TODO: Add your initialization code here

            base.Initialize();
            InitializeCamera();
        }


        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            float timeDelta = (float)gameTime.ElapsedGameTime.TotalSeconds;
            Matrix rotationMatrix;

            if (inputEnabled)
            {
                #region Yaw
                if (input.KeyboardState.IsKeyDown(Keys.Left)
                    || input.GamePads[0].ThumbSticks.Right.X < 0
                    || input.GamePads[0].DPad.Left == ButtonState.Pressed)
                {
                    cameraYaw += (spinRate * timeDelta);
                }

                if (input.KeyboardState.IsKeyDown(Keys.Right)
                    || input.GamePads[0].ThumbSticks.Right.X > 0
                    || input.GamePads[0].DPad.Right == ButtonState.Pressed)
                {
                    cameraYaw -= (spinRate * timeDelta);
                }

#if !XBOX360
                if ((input.PreviousMouseState.X > input.MouseState.X)
                    && (input.MouseState.RightButton == ButtonState.Pressed))
                {
                    cameraYaw += ((spinRate * 2) * timeDelta);
                }
                else if ((input.PreviousMouseState.X < input.MouseState.X)
                    && (input.MouseState.RightButton == ButtonState.Pressed))
                {
                    cameraYaw -= ((spinRate * 2) * timeDelta);
                }
#endif

                if (cameraYaw > 360)
                    cameraYaw -= 360;
                else if (cameraYaw < 0)
                    cameraYaw += 360;
                #endregion

                #region Pitch
                if (input.KeyboardState.IsKeyDown(Keys.Down)
                    || input.GamePads[0].ThumbSticks.Right.Y < 0
                    || input.GamePads[0].DPad.Down == ButtonState.Pressed)
                {
                    cameraPitch -= (spinRate * timeDelta);
                }

                if (input.KeyboardState.IsKeyDown(Keys.Up)
                    || input.GamePads[0].ThumbSticks.Right.Y > 0
                    || input.GamePads[0].DPad.Up == ButtonState.Pressed)
                {
                    cameraPitch += (spinRate * timeDelta);
                }

#if !XBOX360
                if ((input.PreviousMouseState.Y > input.MouseState.Y)
                    && (input.MouseState.RightButton == ButtonState.Pressed))
                {
                    cameraPitch += ((spinRate * 2) * timeDelta);
                }
                else if ((input.PreviousMouseState.Y < input.MouseState.Y)
                    && (input.MouseState.RightButton == ButtonState.Pressed))
                {
                    cameraPitch -= ((spinRate * 2) * timeDelta);
                }
#endif

                if (cameraPitch > 89)
                    cameraPitch = 89;
                else if (cameraPitch < -89)
                    cameraPitch = -89;
                #endregion

                movement *= (moveRate * timeDelta);

                if (input.KeyboardState.IsKeyDown(Keys.F1)
                    || input.GamePads[0].Buttons.LeftStick == ButtonState.Pressed)
                    cameraPosition = new Vector3(cameraPosition.X, startingPosition.Y, cameraPosition.Z);

                if (input.KeyboardState.IsKeyDown(Keys.F2)
                    || input.GamePads[0].Buttons.RightStick == ButtonState.Pressed)
                {
                    cameraPosition = startingPosition;
                    cameraTarget = Vector3.Zero;
                    cameraYaw = 0f;
                    cameraPitch = 0f;
                }

                Matrix.CreateRotationY(MathHelper.ToRadians(cameraYaw), out rotationMatrix);

                if (movement != Vector3.Zero)
                {
                    Matrix rotTemp = rotationMatrix;
                    if (input.KeyboardState.IsKeyDown(Keys.Space)
                        || input.GamePads[0].Buttons.A == ButtonState.Pressed)
                        rotTemp = Matrix.CreateRotationX(MathHelper.ToRadians(cameraPitch)) * rotationMatrix; ;

                    Vector3.Transform(ref movement, ref rotTemp, out movement);
                    cameraPosition += movement;
                }

                rotationMatrix = Matrix.CreateRotationX(MathHelper.ToRadians(cameraPitch)) * rotationMatrix;

                Vector3 transformedReference;
                Vector3.Transform(ref cameraReference, ref rotationMatrix, out transformedReference);

                Vector3.Add(ref cameraPosition, ref transformedReference, out cameraTarget);
            }

            Matrix.CreateLookAt(ref cameraPosition, ref cameraTarget, ref cameraUpVector, out view);

            base.Update(gameTime);
        }

        ///////////////////////////////////////////////////////////////////////////
        ///////////////////////////////////////////////////////////////////////////
        ///////////////////////////////////////////////////////////////////////////
        ///////////////////////////////////////////////////////////////////////////
        ///////////////////////////////////////////////////////////////////////////

        public void InitializeCamera()
        {
            float aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width / (float)graphics.GraphicsDevice.Viewport.Height;

            cameraTarget = Vector3.Zero;
            cameraUpVector = Vector3.Up;

            projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspectRatio, 1.0f, 100000.0f);
            Matrix.CreateLookAt(ref cameraPosition, ref cameraTarget, ref cameraUpVector, out view);
        }
    }
}


